#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include "relayer.h"

#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE 1024

enum
{
  STATE_R = 1, // reading
  STATE_W,     // writing
  STATE_EX,    // exception
  STATE_T      // terminated
};

struct rel_fsm_st
{
  int state;
  int sfd; // source fd
  int dfd; // destination fd
  int len; // read len
  char buf[BUFSIZE];
  int pos;
  int64_t count;
};

struct rel_job_st
{
  int job_state;
  int fd1;
  int fd2;
  struct rel_fsm_st fsm12, fsm21;
  int fd1_save, fd2_save;
};

static struct rel_job_st *rel_job[REL_JOBMAX];
static pthread_mutex_t mut_rel_job = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t init_once = PTHREAD_ONCE_INIT;


static void fsm_driver(struct rel_fsm_st *fsm)
{
  switch (fsm->state) {
    case STATE_R:
	    fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);
	    if (fsm->len == 0) {
	      fsm->state = STATE_T;
	    } else if (fsm->len < 0) {
	      if (errno == EAGAIN) {
	        fsm->state = STATE_R;
	      } else {
	        fsm->state = STATE_EX;
	      }
	    } else {
	      fsm->state = STATE_W;
	      fsm->pos = 0;
	    }
	    break;
    case STATE_W:
	    int n = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
	    if (n < 0) {
	      if (errno == EAGAIN) {
	        fsm->state = STATE_W;
	      } else {
	        fsm->state = STATE_EX;
	      }
	    } else if (n < fsm->len) {
	      fsm->state = STATE_W;
	      fsm->len -= n;
	      fsm->pos += n;
	    } else {
	      fsm->state = STATE_R;
	    }
	    break;
    case STATE_EX:
	    perror("exception");
	    fsm->state = STATE_T;
	    break;
    case STATE_T:
	    /* */
	    break;
    default:
	    printf("fsm_driver: default abort\n");
	    abort();
	    break;

  }
}

static void *thr_relayer(void *arg)
{
  while (1) {
    pthread_mutex_lock(&mut_rel_job);
    for (int i = 0; i < REL_JOBMAX; i++) {
      if (rel_job[i] != NULL) {
        if (rel_job[i]->job_state == STATE_RUNNING) {
	  fsm_driver(&rel_job[i]->fsm12);
	  fsm_driver(&rel_job[i]->fsm21);
	  if (rel_job[i]->fsm12.state == STATE_T && rel_job[i]->fsm21.state == STATE_T)
	    rel_job[i]->job_state = STATE_OVER;
	}
      }
    }
    pthread_mutex_unlock(&mut_rel_job);
  }
}

// create a thread
static void module_load(void)
{
  pthread_t tid_relayer;
  if (pthread_create(&tid_relayer, NULL, thr_relayer, NULL) < 0) {
    perror("pthread_create");
    exit(1);
  }
}

static int get_free_pos()
{
  for (int i = 0; i < REL_JOBMAX; i++) {
    if (rel_job[i] == NULL) {
      return i;
    }
  }
  return -1;
}

/* add a job to array, return the index of the job in the array if success */
int rel_addjob(int fd1, int fd2)
{
  // make the module function executed only once
  pthread_once(&init_once, module_load);

  int pos;
  struct rel_job_st *me = (struct rel_job_st *)malloc(sizeof(*me));
  if (me == NULL)
    return -ENOMEM;
  
  me->fd1 = fd1;
  me->fd2 = fd2;
  me->job_state = STATE_RUNNING;

  // set noblock
  me->fd1_save = fcntl(fd1, F_GETFL);
  fcntl(me->fd1, F_SETFL, me->fd1_save | O_NONBLOCK);
  me->fd2_save = fcntl(fd2, F_GETFL);
  fcntl(me->fd2, F_SETFL, me->fd2_save | O_NONBLOCK);

  me->fsm12.sfd = me->fd1;
  me->fsm12.dfd = me->fd2;
  me->fsm12.state = STATE_R;

  me->fsm21.sfd = me->fd2;
  me->fsm21.dfd = me->fd1;
  me->fsm21.state = STATE_R;

  pthread_mutex_lock(&mut_rel_job);
  pos = get_free_pos();
  if (pos < 0) {
    pthread_mutex_unlock(&mut_rel_job);
    fcntl(me->fd1, F_SETFL, me->fd1_save);
    fcntl(me->fd2, F_SETFL, me->fd2_save);
    free(me);
    return -ENOSPC;
  }
  rel_job[pos] = me;
  pthread_mutex_unlock(&mut_rel_job);

  return pos;
}

/* cancel a job in array, return the job state in rel_stat */
int rel_canceljob(int index, struct rel_stat_st *rel_stat)
{

}

int rel_statjob(int index, struct rel_stat_st *rel_stat){

}
